package com.zyk.scaffold.tcp.server.channel;


import com.zyk.scaffold.common.utils.MD5Util;
import com.zyk.scaffold.common.utils.RandomUtils;
import com.zyk.scaffold.common.utils.StringUtils;
import com.zyk.scaffold.tcp.connector.TcpConnectionManager;
import com.zyk.scaffold.tcp.constant.TcpConstants;
import com.zyk.scaffold.tcp.dto.SystemMessageDTO;
import com.zyk.scaffold.tcp.enums.HeartBeatTypeEnum;
import com.zyk.scaffold.tcp.holder.ContextHolder;
import com.zyk.scaffold.tcp.propertises.TcpProperties;
import com.zyk.scaffold.tcp.proto3.MessageBuf;
import com.zyk.scaffold.tcp.server.handler.MsgHandler;
import com.zyk.scaffold.tcp.session.Session;
import com.zyk.scaffold.tcp.session.SessionManager;
import com.zyk.scaffold.tcp.utils.MsgUtils;
import com.zyk.scaffold.tcp.utils.NetUtils;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

@Slf4j
@ChannelHandler.Sharable
public class TcpServerHandler extends ChannelInboundHandlerAdapter {
    private TcpProperties tcpProperties;
    private SessionManager sessionManager;
    private TcpConnectionManager tcpConnectionManager;

    public TcpServerHandler(TcpProperties tcpProperties, SessionManager sessionManager, TcpConnectionManager tcpConnectionManager) {
        this.tcpProperties = tcpProperties;
        this.sessionManager = sessionManager;
        this.tcpConnectionManager = tcpConnectionManager;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object o) throws Exception {
        try {
            if (o instanceof MessageBuf.JMTransfer) {
                SystemMessageDTO sMsg = generateSystemMessage(ctx);
                MessageBuf.JMTransfer message = (MessageBuf.JMTransfer) o;
                // 过滤心跳
                if (MessageBuf.JMTransfer.MsgType.HEARTBEAT.equals(message.getMsgType())) {
                    if(!HeartBeatTypeEnum.REQUEST.equals(HeartBeatTypeEnum.converter(tcpProperties.getServer().getHeartBeatType()))){
                        return;
                    }
                    sessionManager.refreshHeartbeat(message.getToken(), ctx);
                }
                // 登录处理
                if (MessageBuf.JMTransfer.MsgType.LOGIN.equals(message.getMsgType())) {
                    login(ctx, message);
                    return;
                }
                // 权限校验
                if (!auth(ctx, message)) {
                    MessageBuf.JMTransfer jmTransfer = MsgUtils.buildError("未认证或已超时，请重试");
                    ctx.writeAndFlush(jmTransfer);
                    ctx.close();
                    return;
                }
                publishEvent(ctx, message, sMsg);
            } else {
                log.warn("TcpServerHandler channelRead message is not proto.");
            }
        } catch (Exception e) {
            log.error("TcpServerHandler TcpServerHandler handler error.", e);
            throw e;
        }
    }

    private boolean auth(ChannelHandlerContext ctx, MessageBuf.JMTransfer message) {
        String token = message.getToken();
        Session session = ContextHolder.getSessionManager().getSession(token);
        if(session != null){
            String timeStamp = message.getTimeStamp();
            String appId = message.getAppId();
            String msg = message.getBody().getMsg();
            String sign = message.getSign();
            return sign.equals(sign(token, timeStamp, appId, msg));
        }
        return Boolean.FALSE;
    }

    private String sign(String token, String timeStamp, String appId, String msg) {
        try {
            return MD5Util.computeMD5(StringUtils.join(token, timeStamp, appId, msg));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 登录
     * @param ctx
     * @param message
     */
    private Boolean login(ChannelHandlerContext ctx, MessageBuf.JMTransfer message) {
        String token = RandomUtils.getCharAndNumr(14);
        sessionManager.create(token, ctx);
        MessageBuf.JMTransfer jmTransfer = null;
        try {
            jmTransfer = MsgUtils.buildReplyToken(message, token);
        } catch (Exception e) {
            log.error("LoginMsgHandler 登录响应异常", e);
            tcpConnectionManager.send(token, MsgUtils.buildError("登录异常, 请重试"));
            return Boolean.FALSE;
        }
        ctx.writeAndFlush(jmTransfer);
        ctx.channel().attr(TcpConstants.TCP_SERVER_SESSION_HOOK).set(token);
        return Boolean.TRUE;
    }

    private void publishEvent(ChannelHandlerContext ctx, MessageBuf.JMTransfer message, SystemMessageDTO sMsg) {
        MsgHandler msgHandler = ContextHolder.getMsgHandler(message.getMsgType());
        Boolean handler = msgHandler.handler(ctx, message, sMsg);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.debug("TcpServerHandler Connected from {" +
                NetUtils.channelToString(ctx.channel().remoteAddress(), ctx.channel().localAddress()) + "}");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.debug("TcpServerHandler Disconnected from {" +
                NetUtils.channelToString(ctx.channel().remoteAddress(), ctx.channel().localAddress()) + "}");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        log.debug("TcpServerHandler channelActive from (" + getRemoteAddress(ctx) + ")");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        log.debug("TcpServerHandler channelInactive from (" + getRemoteAddress(ctx) + ")");
        String sessionId0 = getChannelSessionHook(ctx);
        if (StringUtils.isNotBlank(sessionId0)) {
            ContextHolder.getTcpConnectionManager().close(sessionId0);
            log.warn("TcpServerHandler channelInactive, close channel sessionId0 -> " + sessionId0 + ", ctx -> " + ctx.toString());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.warn("TcpServerHandler (" + getRemoteAddress(ctx) + ") -> Unexpected exception from downstream." + cause);
        String sessionId0 = getChannelSessionHook(ctx);
        if (StringUtils.isNotBlank(sessionId0)) {
            log.error("TcpServerHandler exceptionCaught (sessionId0 -> " + sessionId0 + ", ctx -> " + ctx.toString() + ") -> Unexpected exception from downstream." + cause);
        }
    }

    private String getChannelSessionHook(ChannelHandlerContext ctx) {
        return ctx.channel().attr(TcpConstants.TCP_SERVER_SESSION_HOOK).get();
    }

    private SystemMessageDTO generateSystemMessage(ChannelHandlerContext ctx) {
        SystemMessageDTO systemMessage = new SystemMessageDTO();
        systemMessage.setRemoteAddress(getRemoteAddress(ctx));
        systemMessage.setLocalAddress(getLocalAddress(ctx));
        return systemMessage;
    }

    private String getRemoteAddress(ChannelHandlerContext ctx) {
        SocketAddress remote1 = ctx.channel().remoteAddress();
        InetSocketAddress remote = (InetSocketAddress) remote1;
        return NetUtils.toAddressString(remote);
    }

    private String getLocalAddress(ChannelHandlerContext ctx) {
        SocketAddress local1 = ctx.channel().localAddress();
        InetSocketAddress local = (InetSocketAddress) local1;
        return NetUtils.toAddressString(local);
    }
}
